use std::env;
use std::fs::File;
use std::path::{Path, PathBuf};
use clap::Parser;
use crate::bindgen::{Bindings, Builder, Cargo, Config, Error};

#[macro_use]
extern crate log;
extern crate proc_macro2;
#[macro_use]
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;

mod bindgen;

/// Simple program to greet a person
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
    #[arg(short, long, value_name = "PATH")]
    out: Option<String>,

    #[arg(short, long, value_name = "PATH")]
    input: Option<String>,

    #[arg(short, long, value_name = "PATH")]
    config: Option<String>,
}

fn main() {
    env_logger::init();

    let args = Args::parse();


    // Find the input directory
    let input = match &args.input {
        Some(input) => PathBuf::from(input),
        None => env::current_dir().unwrap(),
    };


    let bindings = match load_bindings(&input, &args) {
        Ok(bindings) => bindings,
        Err(msg) => {
            error!("{}", msg);
            error!("Couldn't generate bindings for {}.", input.display());
            std::process::exit(1);
        }
    };

    let output = match &args.out {
        Some(out) => {
            PathBuf::from( out)
        }
        None=> {
            if input.is_dir(){
                input.join("src")
            }else{
                env::current_dir().unwrap()
            }.join("gen_napi.rs")
        }
    };

    info!("gen: {}", output.display());


    bindings.write_to_file(&output);
}

fn load_bindings(input: &Path, args: &Args) -> Result<Bindings, Error> {
    // If a file is specified then we load it as a single source
    if !input.is_dir() {
        // Load any config specified or search in the input directory
        let config = match &args.config {
            Some(c) => Config::from_file(c).unwrap(),
            None => Config::from_root_or_default(
                input
                    .parent()
                    .expect("All files should have a parent directory"),
            ),
        };


        return Builder::new()
            .with_config(config)
            .with_src(input)
            .generate();
    }

    // We have to load a whole crate, so we use cargo to gather metadata
    let lib = Cargo::load(
        input,
        None, None,
        true,
        false,
        false,
        None,
    )?;

    // Load any config specified or search in the binding crate directory
    let config = match &args.config {
        Some(c) => Config::from_file(c).unwrap(),
        None => {
            let binding_crate_dir = lib.find_crate_dir(&lib.binding_crate_ref());

            if let Some(binding_crate_dir) = binding_crate_dir {
                Config::from_root_or_default(binding_crate_dir)
            } else {
                // This shouldn't happen
                Config::from_root_or_default(input)
            }
        }
    };


    Builder::new()
        .with_config(config)
        .with_cargo(lib)
        .generate()
}
